﻿using System.Net;
using System.Threading;
using my_proxy.Utils;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System;


namespace my_proxy.RequestHandlers
{
    /// <summary>
    /// Handler all Client's requests and deliver the web site
    /// </summary>
    public class ProxyListener
    {
        //the conf that holds all the configartion data
        readonly Configuration _conf;
        //File Reader that reads and writes to the whitelist blacklist files 
        readonly FileReader _fileReader;
        //a lock that sync the writing to the files
        readonly object _fileLock = new object();

        private Tripledes triple;

        Mutex m = new Mutex();
        private Dictionary<string, List<DateTime>> userRequest;

        public ProxyListener(Configuration cf, FileReader fr)
        {
            this._conf = cf;
            this._fileReader = fr;
            this._fileLock = new object();
            triple = new Tripledes();
            userRequest = new Dictionary<string, List<DateTime>>();


        }



        //check the black list
        public bool BlackDo(string address)
        {
            Tripledes t = new Tripledes();
            address = t.CutPrefixSufix(address);
            List<string> blacklist = _fileReader.ReadList(_conf.BlackFile);
            foreach (string url in blacklist)
            {
                string toDec = t.decryption(url);
                toDec = t.CutPrefixSufix(toDec);
                if (toDec.Equals(address))
                {
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// the main function,creates a new listener on the given port and sends it contexts to the threads that 
        /// handle the requests
        /// </summary>
        public void HandleReq()
        {
            // Create a listener
            HttpListener listener = new HttpListener();
            // Add the prefix
            listener.Prefixes.Add("http://" + this._conf.Host + ":" + this._conf.Port + "/");
            //add the function that will check if we need to a Authenticate the request
            listener.AuthenticationSchemeSelectorDelegate = this.AuthenticationSchemeForClient;
            //start the listener
            listener.Start();
            while (true)
            {
                HttpListenerContext context = listener.GetContext();

                /***************************************************/
                //check black list
                if (BlackDo(context.Request.Url.Host))
                {
                    //show 403
                    string str = "403: forbidden";
                    context.Response.StatusCode = 403;
                    context.Response.StatusDescription = "Forbidden";
                    context.Response.ContentType = "text/html";
                    context.Response.ContentEncoding = Encoding.UTF8;
                    System.Console.WriteLine("403: forbidden");
                    Stream s = context.Response.OutputStream;
                    s.Write(UTF8Encoding.UTF8.GetBytes("403: forbidden"), 0, str.Length);
                    context.Response.Close();

                    continue;
                }


                if (context.User != null)
                {
                    var identity = (HttpListenerBasicIdentity)context.User.Identity;
                    string password = identity.Password;
                    if (password.Equals(_conf.Password))
                    {
                        string toEnc = triple.Encrypt(context.Request.RemoteEndPoint.Address.ToString());
                        StreamWriter file = new StreamWriter(_conf.WhiteFile, true);
                        file.WriteLine(toEnc);
                        file.Close();



                    }
                    else
                    {

                        //show 403
                        string str = "403: forbidden";
                        context.Response.StatusCode = 403;
                        context.Response.StatusDescription = "Forbidden";
                        context.Response.ContentType = "text/html";
                        context.Response.ContentEncoding = Encoding.UTF8;
                        System.Console.WriteLine("403: forbidden");
                        Stream s = context.Response.OutputStream;
                        s.Write(UTF8Encoding.UTF8.GetBytes("403: forbidden"), 0, str.Length);
                        context.Response.Close();


                        continue;

                    }
                }
              

                string ip = context.Request.LocalEndPoint.Address.ToString();
                if (userRequest.ContainsKey(ip))
                {
                    userRequest[ip].Add(DateTime.UtcNow);

                }
                else
                {
                    List<DateTime> l = new List<DateTime>();
                    l.Add(DateTime.UtcNow);
                    userRequest.Add(ip, l);
                }
                // Specify the authentication delegate.

                List<DateTime> pair = userRequest[ip];

                while (true)
                {
                    DateTime first = pair[0];
                    DateTime last = pair[pair.Count - 1];
                    TimeSpan diff = last - first;
                    if (diff.Seconds <= _conf.Y)
                    {
                        if (pair.Count >= _conf.X)
                        {
                            string tempHelp = "";
                            StreamReader sr = File.OpenText(_conf.WhiteFile);
                            //StreamWriter sw = new StreamWriter("tempFile.txt");

                            string line = "";
                            string encIp = triple.Encrypt(ip);
                            while ((line = sr.ReadLine()) != null)
                            {
                                // line = triple.decryption(line);
                                if (!line.Equals(encIp))
                                    tempHelp = tempHelp + line + '\n';
                            }
                            sr.Close();
                            File.WriteAllText(_conf.WhiteFile, tempHelp);
                            userRequest.Remove(ip);
                        }
                        break;
                    }
                    else  // >Y 
                    {
                        pair.RemoveAt(0);

                    }

                }
                if (!userRequest.ContainsKey(ip))
                {
                    continue;
                }





                OpenRequestHandler requestHandler;
                //choose the state of the proxy - Open / Anonymous
                switch (this._conf.Mode)
                {
                    case ProxyMode.Anonymous:
                          requestHandler = new AnonymousRequestHandler(this._conf, context, this._fileReader, this._fileLock);
                        break;
                    default:
                        requestHandler = new OpenRequestHandler(this._conf, context, this._fileReader, this._fileLock);
                        break;
                }
                new Thread(requestHandler.ProcessRequest).Start();
            }
        }

        /// <summary>
        /// decides if to ask for Authentication from the user by checking the whitlist
        /// </summary>
        /// <param name="request"></param> the given request
        /// <returns></returns>
        private AuthenticationSchemes AuthenticationSchemeForClient(HttpListenerRequest request)
        {
            List<string> whiteusers=_fileReader.ReadList(_conf.WhiteFile);
            foreach (string user in whiteusers)
            {
                string toDec = triple.decryption(user);
                if (request.RemoteEndPoint.Address.ToString().Equals(toDec))
                {
                    return AuthenticationSchemes.Anonymous;
                }
            }
            return AuthenticationSchemes.Basic;
            
                
        }

    }
}
